#include "defs.h"
#include "data.h"
#include "decl.h"

// get the next character from the input file
static int next(void)
{
  int c;
  if(Putback)
  {
    c = Putback;
    Putback = 0;
    return c;
  }

  c = getc(Infile);
  if('\n' == c)
  {
    Line++;
  }
  return c;
}

static int skip(void)
{
  int c;
  c = next();
  while(' ' == c || '\t' == c || '\n' == c || '\f' == c || '\r' == c)
  {
    c = next();
  }
  return c;
}

// return the position of character c in string s.
static int chrpos(const char* s, int c)
{
  char* p;
  p = strchr(s, c);
  return (p ? p - s : -1);
}

// put back an unwanted character
static void putback(int c)
{
  Putback = c;
}

// scan and return an integer literal value from the input file.
static int scanint(int c)
{
  int k, val = 0;
  while((k = chrpos("0123456789", c)) >= 0)
  {
    val = val * 10 + k;
    c = next();
  }

  putback(c);
  return val;
}

// scan an identifier from the input file and store it in buf array, return the identifier's length.
static int scanident(int c, char* buf, int lim)
{
  int i = 0;
  while(isalpha(c) || isdigit(c) || '_' == c)
  {
    if(lim - 1 == i)
    {
      fatal("identifier too long");
    }
    else if(i < lim - 1)
    {
      buf[i++] = c;
    }
    c = next();
  }
  putback(c);
  buf[i] = '\0';
  return i;
}

static int keyword(char* s)
{
  switch(*s)
  {
    case 'c':
      if(!strcmp(s, "char"))
      {
        return T_CHAR;
      }
      break;
    case 'e':
      if(!strcmp(s, "else"))
      {
        return T_ELSE;
      }
      break;
    case 'f':
      if(!strcmp(s, "for"))
      {
        return T_FOR;
      }
      break;
    case 'i':
      if(!strcmp(s, "int"))
      {
        return T_INT;
      }
      if(!strcmp(s, "if"))
      {
        return T_IF;
      }
      break;
    case 'l':
      if(!strcmp(s, "long"))
      {
        return T_LONG;
      }
      break;
    case 'p':
      if(!strcmp(s, "print"))
      {
        return T_PRINT;
      }
      break;
    case 'w':
      if(!strcmp(s, "while"))
      {
        return T_WHILE;
      }
      break;
    case 'v':
      if(!strcmp(s, "void"))
      {
        return T_VOID;
      }
      break;
    case 'r':
      if(!strcmp(s, "return"))
      {
        return T_RETURN;
      }
      break;
  }
  return 0;
}

// a pointer to a rejected token.
static struct token* Rejtoken = NULL;

// reject the token that we just scanned.
void reject_token(struct token* t)
{
  if(Rejtoken != NULL)
  {
    fatal("can't reject token twice");
  }
  Rejtoken = t;
}


// scan and return the next token found in the input.
int scan(struct token* t)
{
  int c, tokentype;
  if(Rejtoken != NULL)
  {
    t = Rejtoken;
    Rejtoken = NULL;
    return 1;
  }
  c = skip();
  switch(c)
  {
    case EOF:
      t->token = T_EOF;
      return 0;
    case '+':
      t->token = T_PLUS;
      break;
    case '-':
      t->token = T_MINUS;
      break;
    case '/':
      t->token = T_SLASH;
      break;
    case '*':
      t->token = T_STAR;
      break;
    case ';':
      t->token = T_SEMI;
      break;
    case '{':
      t->token = T_LBRACE;
      break;
    case '}':
      t->token = T_RBRACE;
      break;
    case '(':
      t->token = T_LPAREN;
      break;
    case ')':
      t->token = T_RPAREN;
      break;
    case '=':
      if((c = next()) == '=')
      {
        t->token = T_EQ;
      }
      else 
      {
        putback(c);
        t->token = T_ASSIGN;
      }
      break;
    case '!':
      if ((c = next()) == '=') {
        t->token = T_NE;
      } else {
        fatalc("Unrecognised character", c);
      }
      break;
    case '<':
      if ((c = next()) == '=') {
        t->token = T_LE;
      } else {
        putback(c);
        t->token = T_LT;
      }
      break;
    case '>':
      if ((c = next()) == '=') {
        t->token = T_GE;
      } else {
        putback(c);
        t->token = T_GT;
      }
      break; 
    default:
      if(isdigit(c))
      {
        t->intvalue = scanint(c);
        t->token = T_INTLIT;
        break;
      }
      else if(isalpha(c) || '_' == c)
      {
        scanident(c, Text, TEXTLEN);
        if(tokentype = keyword(Text))
        {
          t->token = tokentype;
          break;
        }
        t->token = T_IDENT;
        break;
      }
      fatalc("unrecongnised character", c);
  }
  return 1;
}

